/*
* Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved.
*/

#include "dvpp_sqe_update_vir.h"

#include <asm/current.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/gfp.h>

#include "hw_vdavinci.h"
#include "devdrv_interface.h"
#include "virtmng_interface.h"

#include "dvpp_dma_mem_ctrl.h"
#include "dvpp_tlv_decoder.h"
#include "dvpp_cmdlist.h"
#include "dvpp_cmdlist_log.h"
#include "dvpp_cmdlist_context.h"
#include "dvpp_cmdlist_util.h"
#include "dvpp_sqe_update_comm.h"
#include "dvpp_share_mem_ctrl.h"

static void resource_destroy(
    dvpp_comm_para *comm_para, dvpp_save_para *save_para, dvpp_dma_mem *dma_mem, uint64_t *tlv_stru_addr)
{
    dvpp_dma_free(comm_para->pf_id, save_para->cmdbuf_size, dma_mem->vir_addr, dma_mem->phy_addr);
    dma_mem->vir_addr = 0;

    vfree(*tlv_stru_addr);
    *tlv_stru_addr = 0;
}

static uint64_t transfer_to_pm_share_mem_addr(uint64_t vm_share_mem_addr)
{
    return vm_share_mem_addr - g_share_mem_pool->addr + (uintptr_t)(g_share_mem_pool) + sizeof(dvpp_share_mem_pool);
}

static int32_t get_pf_vf_id(uint32_t dev_id, uint32_t *pf_id, uint32_t *vf_id)
{
    int32_t ret = devdrv_get_pfvf_id_by_devid(dev_id, pf_id, vf_id);
    if (ret != 0) {
        DVPP_CMDLIST_LOG_ERROR("devdrv_get_pfvf_id_by_devid fail, ret:%d, dev_id:%u\n", ret, dev_id);
        return -1;
    }

    DVPP_CMDLIST_LOG_DEBUG("dev_id:%d, pf_id:%u, vf_id:%u\n", dev_id, *pf_id, *vf_id);

    return 0;
}

static int32_t dvpp_drv_ready(dvpp_sqe_args *sqe_args, dvpp_comm_para *para)
{
    int32_t ret = 0;
    uint32_t pf_id = 0;
    dvpp_dma_mem dma_mem;
    dvpp_svm_pa_info *pa_info = NULL;
    dvpp_save_para save_para;
    uint64_t tlv_stru_addr = 0;
    uint64_t cmdbuf_size_used = 0;
    // 虚拟机下把相对于共享内存起始地址的偏移存放在args_addr中
    uint64_t offset = sqe_args->args_addr;
    dvpp_share_blk *blk = (dvpp_share_blk*)(offset + (uintptr_t)(g_share_mem_pool));
    sqe_args->args_addr = transfer_to_pm_share_mem_addr(blk->addr);
    pa_info = sqe_args->args_addr + sqe_args->args_size;

    pa_info->pa_list = transfer_to_pm_share_mem_addr(pa_info->pa_list);
    pa_info->size_list = transfer_to_pm_share_mem_addr(pa_info->size_list);

    tlv_stru_addr = (uint64_t)vmalloc(sizeof(dvpp_tlv_type_struct));
    if (tlv_stru_addr == 0) {
        DVPP_CMDLIST_LOG_ERROR("alloc tlv struct addr fail.\n");
        goto FAIL0;
    }

    // 用pf的能力做dma内存申请
    dma_mem.vir_addr = (uint64_t)dvpp_dma_alloc(
        para->pf_id, sqe_args->cmdbuf_size, &dma_mem.phy_addr, GFP_ATOMIC);
    if (dma_mem.vir_addr == 0) {
        DVPP_CMDLIST_LOG_ERROR("alloc host dma memory fail, size:%d\n", sqe_args->cmdbuf_size);
        goto FAIL1;
    }
    sqe_args->cmdbuf_kva = dma_mem.vir_addr;
    // 这里保存是因为生成sqe的时候可能会修改下面的值，后续释放内存就会出错
    save_para.cmdbuf_size = sqe_args->cmdbuf_size;
    if (dvpp_comm_drv_ready(sqe_args, tlv_stru_addr, &cmdbuf_size_used) != 0) {
        DVPP_CMDLIST_LOG_ERROR("dvpp drv comm ready fail.\n");
        goto FAIL2;
    }

    // 用pf的能力做pcie拷贝
    ret = dvpp_dma_sync_link_copy(para->pf_id, &dma_mem, pa_info);
    if (ret != 0) {
        DVPP_CMDLIST_LOG_ERROR("pcie copy host dma memory to device fail. ret:%d\n", ret);
        goto FAIL2;
    }

    dvpp_put_share_mem_blk_to_pool(blk);
    resource_destroy(para, &save_para, &dma_mem, &tlv_stru_addr);
    return 0;

FAIL2:
    dvpp_dma_free(para->pf_id, save_para.cmdbuf_size, dma_mem.vir_addr, dma_mem.phy_addr);
    dma_mem.vir_addr = 0;
FAIL1:
    vfree(tlv_stru_addr);
    tlv_stru_addr = 0;
FAIL0:
    dvpp_put_share_mem_blk_to_pool(blk);
    return -1;
}

static dvpp_share_mem_pool* dvpp_get_share_mem_pool(uint32_t pf_id, uint32_t vf_id, dvpp_sqe_args* sqe_args)
{
    int32_t ret = 0;
    void *vdavinci;
    uint64_t addr;
    int32_t size;
    const int32_t BAR_INDEX = 1;

    vdavinci = vmngh_get_vdavinci_by_id(pf_id, vf_id);
    if (vdavinci == NULL) {
        return NULL;
    }

    ret = hw_dvt_hypervisor_mmio_get((void**)(&addr), &size, vdavinci, BAR_INDEX);
    if (ret != 0) {
        DVPP_CMDLIST_LOG_ERROR("get dvpp share mem addr fail, ret:%d, size:%d\n", ret, size);
        return NULL;
    }

    DVPP_CMDLIST_LOG_DEBUG("dvpp share mem size:%d\n", size);

    return addr;
}

// 虚拟机场景：args内存已是内核态地址，并在后半段保存了svm pa地址信息
int32_t dvpp_sqe_update_vir_mach(uint32_t devid, int pid, void* sqe)
{
    int32_t ret = 0;
    dvpp_sqe_args *sqe_args = NULL;
    dvpp_comm_para comm_para;

    sqe_args = (dvpp_sqe_args*)sqe;

    ret = get_pf_vf_id(devid, &comm_para.pf_id, &comm_para.vf_id);
    if (ret != 0) {
        return -1;
    }

    if (g_share_mem_pool == NULL) {
        spin_lock(&g_share_mem_pool_lock);
        if (g_share_mem_pool == NULL) {
            g_share_mem_pool = dvpp_get_share_mem_pool(comm_para.pf_id, comm_para.vf_id, sqe_args);
        }
        spin_unlock(&g_share_mem_pool_lock);
    }

    if (g_share_mem_pool == NULL) {
        DVPP_CMDLIST_LOG_ERROR("share mem base addr is null.\n");
        return -1;
    }

    comm_para.devid = devid;
    comm_para.pid   = pid;
    if (dvpp_drv_ready(sqe_args, &comm_para) != 0) {
        DVPP_CMDLIST_LOG_ERROR("dvpp drv ready fail.\n");
        return -1;
    }

    return 0;
}
